import java.awt.Point; import java.awt.Graphics; import java.awt.Dimension; import java.awt.Font; import java.awt.Color; import java.awt.Toolkit; import java.awt.event.ActionListener; import java.awt.event.ActionEvent; import java.awt.event.MouseListener; import java.awt.event.MouseEvent; import javax.swing.JFrame; import javax.swing.JPanel; import javax.swing.JButton; import javax.swing.JLabel; import javax.swing.ImageIcon; import java.net.URL; import java.util.List; import java.util.ArrayList; /** * This class provides a GUI for solitaire games related to Elevens. */ public class CardGameGUI extends JFrame implements ActionListener { /** Height of the game frame. */ private static final int DEFAULT_HEIGHT = 302; /** Width of the game frame. */ private static final int DEFAULT_WIDTH = 800; /** Width of a card. */ private static final int CARD_WIDTH = 73; /** Height of a card. */ private static final int CARD_HEIGHT = 97; /** Row (y coord) of the upper left corner of the first card. */ private static final int LAYOUT_TOP = 30; /** Column (x coord) of the upper left corner of the first card. */ private static final int LAYOUT_LEFT = 30; /** Distance between the upper left x coords of * two horizonally adjacent cards. */ private static final int LAYOUT_WIDTH_INC = 100; /** Distance between the upper left y coords of * two vertically adjacent cards. */ private static final int LAYOUT_HEIGHT_INC = 125; /** y coord of the "Replace" button. */ private static final int BUTTON_TOP = 30; /** x coord of the "Replace" button. */ private static final int BUTTON_LEFT = 570; /** Distance between the tops of the "Replace" and "Restart" buttons. */ private static final int BUTTON_HEIGHT_INC = 50; /** y coord of the "n undealt cards remain" label. */ private static final int LABEL_TOP = 160; /** x coord of the "n undealt cards remain" label. */ private static final int LABEL_LEFT = 540; /** Distance between the tops of the "n undealt cards" and * the "You lose/win" labels. */ private static final int LABEL_HEIGHT_INC = 35; /** The board (Board subclass). */ private Board board; /** The main panel containing the game components. */ private JPanel panel; /** The Replace button. */ private JButton replaceButton; /** The Restart button. */ private JButton restartButton; /** The "number of undealt cards remain" message. */ private JLabel statusMsg; /** The "you've won n out of m games" message. */ private JLabel totalsMsg; /** The card displays. */ private JLabel[] displayCards; /** The win message. */ private JLabel winMsg; /** The loss message. */ private JLabel lossMsg; /** The coordinates of the card displays. */ private Point[] cardCoords; /** kth element is true iff the user has selected card #k. */ private boolean[] selections; /** The number of games won. */ private int totalWins; /** The number of games played. */ private int totalGames; /** * Initialize the GUI. * @param gameBoard is a <code>Board</code> subclass. */ public CardGameGUI(Board gameBoard) { board = gameBoard; totalWins = 0; totalGames = 0; // Initialize cardCoords using 5 cards per row cardCoords = new Point[board.size()]; int x = LAYOUT_LEFT; int y = LAYOUT_TOP; for (int i = 0; i < cardCoords.length; i++) { cardCoords[i] = new Point(x, y); if (i % 5 == 4) { x = LAYOUT_LEFT; y += LAYOUT_HEIGHT_INC; } else { x += LAYOUT_WIDTH_INC; } } selections = new boolean[board.size()]; initDisplay(); setDefaultCloseOperation(EXIT_ON_CLOSE); repaint(); } /** * Run the game. */ public void displayGame() { java.awt.EventQueue.invokeLater(new Runnable() { public void run() { setVisible(true); } }); } /** * Draw the display (cards and messages). */ public void repaint() { for (int k = 0; k < board.size(); k++) { String cardImageFileName = imageFileName(board.cardAt(k), selections[k]); URL imageURL = getClass().getResource(cardImageFileName); if (imageURL != null) { ImageIcon icon = new ImageIcon(imageURL); displayCards[k].setIcon(icon); displayCards[k].setVisible(true); } else { throw new RuntimeException( "Card image not found: \"" + cardImageFileName + "\""); } } statusMsg.setText(board.deckSize() + " undealt cards remain."); statusMsg.setVisible(true); totalsMsg.setText("You've won " + totalWins + " out of " + totalGames + " games."); totalsMsg.setVisible(true); pack(); panel.repaint(); } /** * Initialize the display. */ private void initDisplay() { panel = new JPanel() { public void paintComponent(Graphics g) { super.paintComponent(g); } }; // If board object's class name follows the standard format // of ...Board or ...board, use the prefix for the JFrame title String className = board.getClass().getSimpleName(); int classNameLen = className.length(); int boardLen = "Board".length(); String boardStr = className.substring(classNameLen - boardLen); if (boardStr.equals("Board") || boardStr.equals("board")) { int titleLength = classNameLen - boardLen; setTitle(className.substring(0, titleLength)); } // Calculate number of rows of cards (5 cards per row) // and adjust JFrame height if necessary int numCardRows = (board.size() + 4) / 5; int height = DEFAULT_HEIGHT; if (numCardRows > 2) { height += (numCardRows - 2) * LAYOUT_HEIGHT_INC; } this.setSize(new Dimension(DEFAULT_WIDTH, height)); panel.setLayout(null); panel.setPreferredSize( new Dimension(DEFAULT_WIDTH - 20, height - 20)); displayCards = new JLabel[board.size()]; for (int k = 0; k < board.size(); k++) { displayCards[k] = new JLabel(); panel.add(displayCards[k]); displayCards[k].setBounds(cardCoords[k].x, cardCoords[k].y, CARD_WIDTH, CARD_HEIGHT); displayCards[k].addMouseListener(new MyMouseListener()); selections[k] = false; } replaceButton = new JButton(); replaceButton.setText("Replace"); panel.add(replaceButton); replaceButton.setBounds(BUTTON_LEFT, BUTTON_TOP, 100, 30); replaceButton.addActionListener(this); restartButton = new JButton(); restartButton.setText("Restart"); panel.add(restartButton); restartButton.setBounds(BUTTON_LEFT, BUTTON_TOP + BUTTON_HEIGHT_INC, 100, 30); restartButton.addActionListener(this); statusMsg = new JLabel( board.deckSize() + " undealt cards remain."); panel.add(statusMsg); statusMsg.setBounds(LABEL_LEFT, LABEL_TOP, 250, 30); winMsg = new JLabel(); winMsg.setBounds(LABEL_LEFT, LABEL_TOP + LABEL_HEIGHT_INC, 200, 30); winMsg.setFont(new Font("SansSerif", Font.BOLD, 25)); winMsg.setForeground(Color.GREEN); winMsg.setText("You win!"); panel.add(winMsg); winMsg.setVisible(false); lossMsg = new JLabel(); lossMsg.setBounds(LABEL_LEFT, LABEL_TOP + LABEL_HEIGHT_INC, 200, 30); lossMsg.setFont(new Font("SanSerif", Font.BOLD, 25)); lossMsg.setForeground(Color.RED); lossMsg.setText("Sorry, you lose."); panel.add(lossMsg); lossMsg.setVisible(false); totalsMsg = new JLabel("You've won " + totalWins + " out of " + totalGames + " games."); totalsMsg.setBounds(LABEL_LEFT, LABEL_TOP + 2 * LABEL_HEIGHT_INC, 250, 30); panel.add(totalsMsg); if (!board.anotherPlayIsPossible()) { signalLoss(); } pack(); getContentPane().add(panel); getRootPane().setDefaultButton(replaceButton); panel.setVisible(true); } /** * Deal with the user clicking on something other than a button or a card. */ private void signalError() { Toolkit t = panel.getToolkit(); t.beep(); } /** * Returns the image that corresponds to the input card. * Image names have the format "[Rank][Suit].GIF" or "[Rank][Suit]S.GIF", * for example "aceclubs.GIF" or "8heartsS.GIF". The "S" indicates that * the card is selected. * * @param c Card to get the image for * @param isSelected flag that indicates if the card is selected * @return String representation of the image */ private String imageFileName(Card c, boolean isSelected) { String str = "cards/"; if (c == null) { return "cards/back1.GIF"; } str += c.rank() + c.suit(); if (isSelected) { str += "S"; } str += ".GIF"; return str; } /** * Respond to a button click (on either the "Replace" button * or the "Restart" button). * @param e the button click action event */ public void actionPerformed(ActionEvent e) { if (e.getSource().equals(replaceButton)) { // Gather all the selected cards. List<Integer> selection = new ArrayList<Integer>(); for (int k = 0; k < board.size(); k++) { if (selections[k]) { selection.add(new Integer(k)); } } // Make sure that the selected cards represent a legal replacement. if (!board.isLegal(selection)) { signalError(); return; } for (int k = 0; k < board.size(); k++) { selections[k] = false; } // Do the replace. board.replaceSelectedCards(selection); if (board.isEmpty()) { signalWin(); } else if (!board.anotherPlayIsPossible()) { signalLoss(); } repaint(); } else if (e.getSource().equals(restartButton)) { board.newGame(); getRootPane().setDefaultButton(replaceButton); winMsg.setVisible(false); lossMsg.setVisible(false); if (!board.anotherPlayIsPossible()) { signalLoss(); lossMsg.setVisible(true); } for (int i = 0; i < selections.length; i++) { selections[i] = false; } repaint(); } else { signalError(); return; } } /** * Display a win. */ private void signalWin() { getRootPane().setDefaultButton(restartButton); winMsg.setVisible(true); totalWins++; totalGames++; } /** * Display a loss. */ private void signalLoss() { getRootPane().setDefaultButton(restartButton); lossMsg.setVisible(true); totalGames++; } /** * Receives and handles mouse clicks. Other mouse events are ignored. */ private class MyMouseListener implements MouseListener { /** * Handle a mouse click on a card by toggling its "selected" property. * Each card is represented as a label. * @param e the mouse event. */ public void mouseClicked(MouseEvent e) { for (int k = 0; k < board.size(); k++) { if (e.getSource().equals(displayCards[k]) && board.cardAt(k) != null) { selections[k] = !selections[k]; repaint(); return; } } signalError(); } /** * Ignore a mouse exited event. * @param e the mouse event. */ public void mouseExited(MouseEvent e) { } /** * Ignore a mouse released event. * @param e the mouse event. */ public void mouseReleased(MouseEvent e) { } /** * Ignore a mouse entered event. * @param e the mouse event. */ public void mouseEntered(MouseEvent e) { } /** * Ignore a mouse pressed event. * @param e the mouse event. */ public void mousePressed(MouseEvent e) { } } }